#include "io_config.h"
#if API_ENABLE_CONFIG_IO
#include "http_server.h"
#include "driver/gpio.h"
#include "api_map.h"
#include "config.h"

#include "esp_log.h"
#define TAG "Output"
#define log_e(...) ESP_LOGE(TAG, __VA_ARGS__)
#define log_i(...) ESP_LOGI(TAG, __VA_ARGS__)
#define log_w(...) ESP_LOGW(TAG, __VA_ARGS__)

#define GET_CONFIG_GPIO(num) ((num) == 255 ? -1 : (num))
#define CONFIG_ITEM_NAME "OutputConfig"
#define OUTPUT_MAX 16
#define MODE_LIST_MAX (sizeof(mode_list) / sizeof(mode_list[0]))

#pragma pack(1)
typedef struct mode_item
{
    const char *const name;
    uint8_t value;
} mode_item_t;

typedef struct storage
{
    uint8_t io;
    uint8_t mode;
    char name[32];
} storage_s, *storage_t;

typedef struct configs
{
    uint8_t enable : 1; // 是否启用
    uint8_t len : 7;    // 按钮数量
    storage_s datas[OUTPUT_MAX];
} configs_s, *configs_t;

typedef struct output_data
{
    configs_s config; // 配置
} output_data_s, *output_data_t;
#pragma pack()
static const mode_item_t mode_list[] = {
    {"低电平", 0},
    {"高电平", 1},
};
output_data_t output_datas = NULL;

static bool config_output_gpio(void)
{
    if (output_datas->config.enable)
    {
        gpio_config_t io_conf = {
            .mode = GPIO_MODE_OUTPUT,
            .pull_down_en = 0,
            .pull_up_en = 0};
        esp_err_t err = ESP_OK;
        for (uint8_t i = 0; i < output_datas->config.len; i++)
        {
            int io = GET_CONFIG_GPIO(output_datas->config.datas[i].io);
            if (io == -1)
                continue;
            io_conf.pull_down_en = 0;
            io_conf.pull_up_en = 0;
            io_conf.pin_bit_mask =
                (1ULL << io);
            if (output_datas->config.datas[i].mode == 0)
                io_conf.pull_up_en = 1;
            else
                io_conf.pull_down_en = 1;
            err = gpio_config(&io_conf);
            if (err != ESP_OK)
            {
                log_e("gpio_config err %d", err);
                return false;
            }
        }
    }
    return true;
}

static bool this_config_init(void)
{
    output_datas = malloc(sizeof(output_data_s));
    log_i("malloc button_config(%d) : %p", sizeof(output_data_s), output_datas);
    if (!output_datas)
    {
        log_e("malloc button_data failed");
        return false;
    }
    memset(output_datas, 0, sizeof(output_data_s));
    if (!config_get(CONFIG_ITEM_NAME, (uint8_t *)&output_datas->config, sizeof(output_data_s)))
    {
        log_w("Not find " CONFIG_ITEM_NAME ", Use default config");
        output_datas->config.len = 1;
        output_datas->config.datas[0].io = -1;
        strcpy(output_datas->config.datas[0].name, "Output0");
    }
    config_output_gpio();
    return true;
}
static bool this_config_save(void)
{
    if (config_set(CONFIG_ITEM_NAME, 1 + sizeof(storage_s) * output_datas->config.len, (uint8_t *)&output_datas->config))
        return config_commit();
    return false;
}

/**
 *  button:[
 *      {
 *          name:"button1",
 *          list:{
 *              state:{
 *                  "name":"状态",
 *                  "type": 0, // 0:数字 1:字符串 2:函数
 *                  "value":0
 *              }
 *          }
 *      }
 *  ]
 * */

static uint16_t
output(http_server_t server, httpd_ws_frame_t *frame, void *data)
{
    if (data) /* set */
    {
        /*
        {
            "output":[
                {
                    "enable":true,
                    "params":{
                        "mode":["低电平","高电平"],
                    },
                    "gpio":[
                        {"io":-1, "mode":0, "name":"Output0"},
                        {"io":-1, "mode":0, "name":"Output1"},
                    ]
                }
            ],
            ...
        }
         */
        if (cJSON_HasObjectItem(data, "output"))
        {
            log_i("process output config");
            cJSON *output_item = cJSON_GetObjectItem(data, "output");
            if (!cJSON_IsArray(output_item))
                goto config_error;

            cJSON *item = cJSON_GetArrayItem(output_item, 0);
            if (!cJSON_IsObject(item))
                goto config_error;

            cJSON *enable_item = cJSON_GetObjectItem(item, "enable");
            cJSON *gpio_item = cJSON_GetObjectItem(item, "gpio");
            if (!enable_item || !gpio_item || !cJSON_IsArray(gpio_item) || !cJSON_IsBool(enable_item))
                goto config_error;

            uint8_t change = 0;
            uint8_t enable = cJSON_IsTrue(enable_item);
            uint8_t array_len = cJSON_GetArraySize(gpio_item);
            uint8_t io[OUTPUT_MAX] = {0};
            uint8_t mode[OUTPUT_MAX] = {0};

            if (array_len > OUTPUT_MAX)
                goto config_error;

            if (output_datas->config.len != array_len)
                change = 1;
            if (enable != output_datas->config.enable)
                change = 1;
            for (int i = 0; i < output_datas->config.len; i++)
            {
                io[i] = output_datas->config.datas[i].io;
                mode[i] = output_datas->config.datas[i].mode;
            }

            for (int i = 0; i < array_len; i++)
            {
                if (i >= output_datas->config.len)
                    continue;
                cJSON *item = cJSON_GetArrayItem(gpio_item, i);
                cJSON *io_item = cJSON_GetObjectItem(item, "io");
                cJSON *mode_item = cJSON_GetObjectItem(item, "mode");
                cJSON *name_item = cJSON_GetObjectItem(item, "name");
                if (!io_item || !mode_item || !name_item)
                    goto config_error;

                if (!cJSON_IsNumber(io_item) || !cJSON_IsNumber(mode_item) || !cJSON_IsString(name_item))
                    goto config_error;

                if (io_item->valueint != -1 && !GPIO_IS_VALID_GPIO(io_item->valueint))
                    goto config_error;
                if (mode_item->valueint >= MODE_LIST_MAX)
                    goto config_error;
                if (GET_CONFIG_GPIO(io[i]) != io_item->valueint || mode[i] != mode_item->valueint)
                {
                    io[i] = io_item->valueint;
                    mode[i] = mode_item->valueint;
                    change = 1;
                }
                if (strncmp(output_datas->config.datas[i].name, name_item->valuestring, 31) != 0)
                    change = 1;
            }

            if (change)
            {
                char *str = (char *)server->data;
                uint16_t source_len = strlen(str);
                uint16_t len = source_len;
                len += sprintf(&str[len], "\"output\":[");
                log_w("output change : enable:%d len:%d", enable, array_len);
                for (int i = 0; i < array_len; i++)
                {
                    cJSON *item = cJSON_GetArrayItem(gpio_item, i);
                    cJSON *name_item = cJSON_GetObjectItem(item, "name");
                    output_datas->config.datas[i].io = GET_CONFIG_GPIO(io[i]);
                    output_datas->config.datas[i].mode = mode[i];
                    strncpy(output_datas->config.datas[i].name, name_item->valuestring, 31);
                    output_datas->config.datas[i].name[31] = '\0';
                }
                bool success = config_output_gpio();
                if (success)
                {
                    output_datas->config.enable = enable;
                    output_datas->config.len = array_len;
                    if (!this_config_save())
                        len += sprintf(&str[len], "{\"code\":%d, \"msg\":\"保存失败\"},", -1);
                    else
                        len += sprintf(&str[len], "{\"code\":%d},", 1);
                }
                else
                    len += sprintf(&str[len], "{\"code\":%d, \"msg\":\"配置失败\"},", -1);
                len -= 1;
                len += sprintf(&str[len], "],");
                return len - source_len;
            }
        }
        return 0;
    config_error:
        char *str = (char *)server->data;
        uint16_t source_len = strlen(str);
        uint16_t len = source_len;
        len += sprintf(&str[len], "\"output\":[");
        len += sprintf(&str[len], "{\"code\":%d, \"msg\":\"参数错误\"}", -1);
        len += sprintf(&str[len], "],");
        return len - source_len;
    }
    else /* get */
    {
        char *str = (char *)server->data;
        uint16_t source_len = strlen(str);
        uint16_t len = source_len;
        len += sprintf(&str[len], "\"output\":[");
        len += sprintf(&str[len], "{");
        len += sprintf(&str[len], "\"enable\":%s,", output_datas->config.enable ? "true" : "false");
        len += sprintf(&str[len], "\"params\":{");
        len += sprintf(&str[len], "\"mode\":[");
        for (int i = 0; i < MODE_LIST_MAX; i++)
            len += sprintf(&str[len], "\"%s\",", mode_list[i].name);
        len -= 1;
        len += sprintf(&str[len], "]");
        len += sprintf(&str[len], "},");
        len += sprintf(&str[len], "\"gpio\":[");
        for (int i = 0; i < output_datas->config.len; i++)
        {
            len += sprintf(&str[len],
                           "{"
                           "\"io\":%d,"
                           "\"mode\":%d,"
                           "\"name\":\"%s\""
                           "},",
                           GET_CONFIG_GPIO(output_datas->config.datas[i].io),
                           output_datas->config.datas[i].mode,
                           output_datas->config.datas[i].name);
        }
        if (output_datas->config.len)
            len -= 1;
        len += sprintf(&str[len], "]}],");
        return len - source_len;
    }
}

static void output_config_init(void)
{
    this_config_init();
    io_config_add(output);
}
ADD_API(output_config_init)
#endif
